/*
 *  Copyright (C) 2010 glauco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.common.defaults;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.balisunrise.annotation.EventType;
import org.balisunrise.annotation.VisualActionTarget;
import org.balisunrise.annotation.util.ActionUtilities;
import org.balisunrise.annotation.util.AnnotationsUtilitiesException;
import org.balisunrise.application.Application;
import org.balisunrise.builder.ActionBuilder;
import org.balisunrise.builder.BuilderException;
import org.balisunrise.common.Action;
import org.balisunrise.common.ActionCollection;
import org.balisunrise.common.ActionProvider;
import org.balisunrise.common.FieldAction;
import org.balisunrise.common.VisualAction;
import org.balisunrise.common.VisualEntity;

/**
 * Contém classes de EventMaps de entidades e disponibiliza ActionCollection.
 *
 * @author Glauco
 */
public class DefaultActionProvider implements ActionProvider{

    // <editor-fold defaultstate="collapsed" desc="PropertyChangeSupport">
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
    public static final String PROP_ENTITYCLASS = "entityClass";
    public static final String PROP_ACTION_MAPS = "actionMaps";

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(listener);
    }
    //</editor-fold>

    /**
     * Classe da entidade que os eventos representão.
     */
    private Class entityClass;
    
    /**
     * Lista com as classes de mapas de eventos da entidade.
     */
    private List<Class> actionMaps;

    /**
     * Mapa com as ActionCollections de uma VisualEntity.
     */
    private Map<VisualEntity,ActionCollection> actionCollections;

    /**
     * Lista com os utilitários das ações dos mapas de ação.
     */
    private List<ActionUtilities> utilities;

    public DefaultActionProvider(Class entityClass) {
        this.entityClass = entityClass;
        actionMaps = new ArrayList<Class>();
        actionCollections = new HashMap<VisualEntity, ActionCollection>();
        utilities = new ArrayList<ActionUtilities>();
    }

    /**
     * Atribui a lista de mapas de eventos.
     * @param actionMaps mapa de ações.
     */
    public void setActionMaps(List<Class> actionMaps) {

        List<Class> old = this.actionMaps;
        this.actionMaps = actionMaps;

        utilities.clear();
        for (Class c : actionMaps) {
            actionMapAdded(c);
        }

        propertyChangeSupport.firePropertyChange(PROP_ACTION_MAPS,
                old, actionMaps);
    }

    /**
     * Adiciona um mapa de ações a lista.
     * @param actionMap mapa de ações.
     */
    public void addActionMap(Class actionMap){
        if(!actionMaps.contains(actionMap)){
            actionMaps.add(actionMap);
            actionMapAdded(actionMap);
        }
    }

    /**
     * Remove um mapa de ações da lista.
     * @param actionMap mapa de ações.
     */
    public boolean removeActionMap(Class actionMap){
        boolean removed = actionMaps.remove(actionMap);
        if(removed)
            actionMapRemoved(actionMap);
        return removed;
    }
    
    /**
     * Set the value of entityClass
     *
     * @param entityClass new value of entityClass
     */
    public void setEntityClass(Class entityClass) {
        Class oldEntityClass = this.entityClass;
        this.entityClass = entityClass;
        propertyChangeSupport.firePropertyChange(PROP_ENTITYCLASS,
                oldEntityClass, entityClass);
    }

    /**
     * Processa um mapa de ação, pegando as ações dele.
     * @param actionMap mapa de ações.
     */
    private void actionMapAdded(Class actionMap){
        try {
            utilities.addAll(ActionUtilities.createAction(actionMap));
        } catch (AnnotationsUtilitiesException ex) {
            Application.get().getLogger().log(Level.SEVERE,
                    "Falha ao ler Annotation do ActionMap.", ex);
        }
        try {
            utilities.addAll(ActionUtilities.createVisualActions(actionMap));
        } catch (AnnotationsUtilitiesException ex) {
            Application.get().getLogger().log(Level.SEVERE,
                    "Falha ao ler Annotation do ActionMap.", ex);
        }
    }

    /**
     * Processa um mapa de ação removido, removendo os ActionUtilities.
     * @param actionMap mapa de ações.
     */
    private void actionMapRemoved(Class actionMap){
        List<ActionUtilities> toRemove = new ArrayList<ActionUtilities>();
        for (ActionUtilities au : utilities) {
            if (au.actionMapClass.equals(actionMap))
                toRemove.add(au);
        }
        if(toRemove.size() > 0)
            utilities.removeAll(toRemove);
    }

    /**
     * Retorna a classe da entidade dos eventos.
     */
    @Override
    public Class getEntityClass() {
        return entityClass;
    }

    /**
     * Retorna os mapas de eventos da entidade;
     */
    @Override
    public Class[] getActionMaps() {
        return actionMaps.toArray(new Class[actionMaps.size()]);
    }

    /**
     * Retorna um ActionCollection.
     *
     * @param eventType Tipo de Evento desejado.
     * @param alias Alias desejado.
     * @return Um Array de BaliEvent. Não retorna nulo, se não encontrar nada
     * retornará um Array vazio.
     */
    @Override
    public ActionCollection createCollection() {

        List maps = new ArrayList();

        for (Class em : actionMaps) {
            maps.add(em);
        }

        ActionCollection ac =
                new DefaultActionCollection(entityClass, maps);

        return ac;
    }

    /**
     * Retorna a instância de ActionCollection criada por uma VisualEntity.<br>
     * Quando a instância da ActionCollection ainda não existir é criado e
     * registrado a ActionCollection para a VisualEntity.
     * @param visualEntity a Top VisualEntity das árvores de proprietários.
     * @return A instância de ActionCollection da VisualEntity.
     */
    @Override
    public ActionCollection getActionCollection(VisualEntity visualEntity) {

        ActionCollection ac = null;

        if( actionCollections.containsKey(visualEntity))
            ac = actionCollections.get(visualEntity);
        else{
            ac = new DefaultActionCollection(entityClass, actionMaps);
            actionCollections.put(visualEntity, ac);
        }

        return ac;
    }

    /**
     * Registra uma ActionCollection para a VisualEntity.
     *
     * @param visualEntity a VisualEntity.
     * @param actionCollection a ActionCollection.
     */
    @Override
    public void registerActionCollection(VisualEntity visualEntity, 
            ActionCollection actionCollection) {
        
        if( actionCollections.containsKey(visualEntity))
            return ;

        actionCollections.put(visualEntity, actionCollection);
    }

     /**
     * Remove o registor de uma ActionCollection para a VisualEntity.
     *
     * @param visualEntity a VisualEntity.
     */
    @Override
    public void removeActionCollection(VisualEntity visualEntity) {

        if( ! actionCollections.containsKey(visualEntity))
            return ;

        actionCollections.remove(visualEntity);
    }

    /**
     * Retorna um Array de Action para o tipo e alias desejado.
     *
     * @param eventType Tipo de Evento desejado.
     * @param alias Alias desejado.
     * @param property Propriedade adicional, utilizada dependendo do tipo de
     * evento.
     * @return Um Array de Action. Não retorna nulo, se não encontrar nada
     * retornará um Array vazio.
     */
    @Override
    public Action[] getActions(EventType eventType, String alias, String property) {

        List<Action> result = new ArrayList<Action>();

        ActionBuilder builder = Application.get()
                .getVisualEntityManager()
                .getActionBuilder();

        for (ActionUtilities au : utilities) {
            if(au.verify(eventType, entityClass, alias, property))
                try{
                    result.add(builder.createAction(au));
                }catch(BuilderException ex){
                    Application.get().getLogger().log(Level.SEVERE,
                            "Falha ao Criar ações da Coleção de Ações", ex);
                }
        }

        return result.toArray(new Action[result.size()]);
    }

    /**
     * Retorna um Array de VisualAction do tipo desejado.
     * Só serão aceitos tipo de evento de entidade visual.
     *
     * @param targets Alvo desejado para a ação visual.
     * @param alias Alias desejado.
     * @param property Propriedade adicional, utilizada dependendo do tipo de
     * evento.
     * @return Um Array de VisualAction. Não retona nulo, se não encontrar nada
     * retornará um Array vazio.
     */
    @Override
    public VisualAction[] getVisualActions(VisualActionTarget targets,
            String alias, String property) {

        List<VisualAction> result = new ArrayList<VisualAction>();

        ActionBuilder builder = Application.get()
                .getVisualEntityManager()
                .getActionBuilder();

        for (ActionUtilities au : utilities) {
            if(au.verify(targets, entityClass, alias, property))
                try{
                    result.add(builder.createVisualAction(au));
                }catch(BuilderException ex){
                    Application.get().getLogger().log(Level.SEVERE,
                            "Falha ao Criar ações visuais da Coleção de Ações",
                            ex);
                }
        }

        return result.toArray(new VisualAction[result.size()]);
    }

    @Override
    public FieldAction[] getFieldActions(String alias, String property) {

        List<FieldAction> result = new ArrayList<FieldAction>();

        ActionBuilder builder = Application.get()
                .getVisualEntityManager()
                .getActionBuilder();

        for (ActionUtilities au : utilities) {
            if(au.verify(VisualActionTarget.FIELD, entityClass, alias, property))
                try{
                    result.add(builder.createFieldAction(au));
                }catch(BuilderException ex){
                    Application.get().getLogger().log(Level.SEVERE,
                            "Falha ao Criar ações de campos da Coleção de Ações"
                            , ex);
                }
        }

        return result.toArray(new FieldAction[result.size()]);
    }

}
